package distributelock

import (
	"lnzw/engine/log"
	"math/rand"
	"time"

	"go.uber.org/zap"
)

const (
	REDIS = iota
	ETCD  = iota
)

const (
	lock_timeout = 5 * time.Second
	lock_prefix  = "distribute_lock:lock:"
)

// 获取锁的重试策略
const (
	LOCK_RETRY_STABLE_INTERVAL     = iota //固定间隔
	LOCK_RETRY_EXPONENTIAL_BACKOFF        //指数退避
	LOCK_RETRY_RANDOM_BACKOFF             //随机退避
)

type IDistributeLock interface {
	Init() error
	Lock(key string) (bool, error)
	UnLock(key string) (bool, error)
}

type Options struct {
	MaxLockCount int           // 最大尝试锁定次数 <=0表示一直尝试锁 一旦填入该次数，是会直接放弃锁的。个别数据不需要太准确的场景可能会使用
	RetryType    int           //重试类型
	LockInterval time.Duration // 每次尝试锁定间隔
}

type DistributeLock struct {
	lockImp IDistributeLock
	ops     Options
}

func NewDistributeLock(lockType int, ops Options) *DistributeLock {
	var lock IDistributeLock
	switch lockType {
	case REDIS:
		lock = &redisLock{}
	case ETCD:
		lock = &etcdLock{}
	}
	lock.Init()
	if ops.LockInterval == 0 {
		ops.LockInterval = 100 * time.Millisecond
	}
	return &DistributeLock{
		lockImp: lock,
		ops:     ops,
	}
}

func (lock *DistributeLock) Lock(key string) {

	if lock.ops.MaxLockCount <= 0 {
		count := 0
		for {
			if rs := lock.lockImpl(key, count); rs {
				return
			}
		}
	} else {
		for count := 0; count < lock.ops.MaxLockCount; count++ {
			if rs := lock.lockImpl(key, count); rs {
				return
			}
		}
	}
}

func (lock *DistributeLock) lockImpl(key string, count int) bool {
	result, err := lock.lockImp.Lock(key)
	if err != nil {
		log.Logger.Error("lock failed", zap.Error(err))
		return false
	}
	if result {
		return true
	}
	log.Logger.Warn("lock failed, need retry")
	var nextInterval time.Duration
	switch lock.ops.RetryType {
	case LOCK_RETRY_STABLE_INTERVAL:
		nextInterval = lock.ops.LockInterval
	case LOCK_RETRY_EXPONENTIAL_BACKOFF:
		nextInterval = lock.ops.LockInterval * (1 << count)
	case LOCK_RETRY_RANDOM_BACKOFF:
		// 100ms-500ms
		nextInterval = time.Duration(rand.Intn(100)+400) * time.Millisecond
	}
	time.Sleep(nextInterval)
	return false
}

func (lock *DistributeLock) UnLock(key string) {
	if lock.lockImp != nil {
		lock.lockImp.UnLock(key)
	}
}
